import R, { and, any, compose, toLower } from "ramda"
// 对数组求和、求积、计数、全判等操作
// 共同点：遍历数组
// 变量：acc 初始值、二元操作（acc op elem）
//      求和：acc 初始 0，二元操作 + elem
//      求积：acc 初始 1，二元操作 * elem
//      计数：acc 初始 0，二元操作 + 1
//      全判：acc 初始 true，二元操作 && judge(elem)
// 我们可以将这类对数组的操作抽象为一个基本操作：reduce

type BinOp<S, T> = (acc: S, elem: T) => S;  // 二元运算
type Aggregate<S, T> = (list: T[]) => S;   // 聚合运算
// reduct 接收一个二元运算和初值，返回一个聚合运算
const reduce = <S, T>(f: BinOp<S, T>, identity: S): Aggregate<S, T> =>
    (list: T[]) => {
        let acc = identity;
        for (let elem of list) {
            acc = f(acc, elem)
        }
        return acc;
    }

// sum
const add: BinOp<number, number> = (acc, elem) => acc + elem;
const sum = reduce(add, 0);
const res1 = sum([1, 2, 3, 4, 5]);
console.log("res1 =", res1);


// product
const mul: BinOp<number, number> = (acc, elem) => acc * elem;
const product = reduce(mul, 1);

const res2 = product([1, 2, 3, 4, 5]);
console.log("res2 =", res2);


// count
const count = reduce(
    (acc, _) => acc + 1,
    0
);

const res3 = count([1, 2, 3, 4, 5]);
console.log("res3 =", res3);

// max
const max = (ns: number[]): number => reduce(
    (acc: number, elem: number) => elem > acc ? elem : acc,
    ns[0]
)(ns)

const res4 = max([1, 2, 4, 6, 3]);
console.log("res4 =", res4)

const resr = max([])
console.log("resr =", resr)

// forAll：由于需要两个参数与 reduce 不符，因此 curry 一下，先接收一个判断操作
type Judge<T> = (val: T) => boolean;
const forAll = <T>(judge: Judge<T>) =>
    reduce(
        (acc: boolean, elem: T) => acc && judge(elem),
        true
    );

const res5 = forAll((e: number) => e > 0)([1, 2, 3, 4, 5]);
console.log("res5 =", res5);

// map：对 list 中每个元素都执行一元操作 f，返回一个数组
// 由于需要两个参数与 reduce 不符，因此 curry 一下，先接收一个一元操作
// 注意返回值其实是一个数组，因此 acc 类型为 S[]
type MonaOp<S, T> = (e: T) => S;
const map = <S, T>(f: MonaOp<S, T>) =>
    reduce(
        (acc: S[], elem: T) => [...acc, f(elem)],
        []
    );


const res6 = map((e: number) => e + 1)([1, 2, 3, 4, 5]);
console.log("res6 =", res6);


// filter：对 list 中每个元素进行判断，返回一个数组
// 由于需要两个参数与 reduce 不符，因此 curry 一下，先接收一个判断操作
// 注意返回值其实是一个原类型的数组，因此 acc 类型为 T[]
const filter = <T>(judge: Judge<T>) =>
    reduce(
        (acc: T[], elem: T) => judge(elem) ? [...acc, elem] : acc,
        []
    );


const res7 = filter((e: number) => e > 3)([1, 2, 3, 4, 5]);
console.log("res7 =", res7);

// composeAll：将操作数组 compose 起来
type Composable = (v: any) => any;
const composeAll = reduce<Composable, Composable>(R.compose, R.identity)

const inc = R.add(1)
const times3 = R.curry(mul)(3)
const times3ThenInc = composeAll([inc, times3])
const res8 = times3ThenInc(3)
console.log("res8 =", res8);


// 练习：将以下操作用 FP 方式表达
const text = "Dirac,Bob,Jason,Bill,Jack"
var names = (text.split(",")).sort();
console.log("names =", names)

var total = 0;
for (let i = 1; i <= names.length; i++) {
    let c = (names[i - 1]).toLowerCase();
    let sum = 0;
    for (let j = 0; j <= c.length - 1; j++) {
        sum = sum + c.charCodeAt(j) - 'A'.charCodeAt(0) + 1;
    }
    total = total + (sum * i);
}

console.log("total =", total);

// 分析
// text => words => sorted words
// one word => value of one word
// values of words => scores of words => total score
console.log("names =", names);

const valueOfWord = (word: string) =>
    word.split('')
        .map(x => x.charCodeAt(0) - 'A'.charCodeAt(0) + 1)
        .reduce(add, 0);

const valuesOfWords = (words: string[]) =>
    words.sort()
        .map(toLower)
        .map(valueOfWord);

const scoresOfWords = (words: string[]) =>
    valuesOfWords(words)
        .map((v, i) => v * (i + 1));

const totalScoreOfWords = (words: string[]) =>
    scoresOfWords(words)
        .reduce(add, 0);

console.log("totalScore =", totalScoreOfWords(names));

// 练习：写一个函数 avg 计算数组中元素均值，数组为空则返回 0
// 下面这种实现当然可以，但是效率很慢
// const avg = (list: number[]) => {
//     const c = count(list);
//     return c !== 0 ? sum(list) / c : 0;
// }

const productF = <T, S>(f0: (...args: readonly T[]) => S, f1: (...args: readonly T[]) => S) => (arg: T[], ...args: readonly T[]) => {
    return [f0(arg[0], ...args), f1(arg[1], ...args)]
}

const avg = (list: number[]) => {
    let [s, c] = reduce(
        productF(add, inc),
        [0, 0])(list);
    return s / c;
}

console.log("avg([1, 2, 3, 4, 5]) =", avg([1, 2, 3, 4, 5]));
console.log("avg([]) =", avg([]));


// Counter FP 示例
type ActionType = "inc" | "dec";

interface Action {
    type: ActionType
}

const reducer = (state = { count: 0 }, action: Action) => {
    switch (action.type) {
        case "inc": {
            return { count: state.count + 1 };
        }
        case "dec": {
            return { count: state.count - 1 };
        }
        default: { return state };
    }
}

let state = { count: 10 };
const incAct: Action = { type: "inc" };
const decAct: Action = { type: "dec" };

state = reducer(state, incAct);
console.log("state =", state);